const {
    ServiceType,
    appendServices
} = require("./base");
const fs = require('fs');
const util = require("../util");
const amqp = require("../amqp");
const config = util.config;
const path = require('path');
const Task = require('../classes/Task');
const { generateKey } = require("crypto");

const routers = {
    "/task/add": ServiceType(),
    "/task/del": ServiceType(),
    "/task/up": ServiceType(),
    "/task/all": ServiceType(),
    "/task/get": ServiceType(),
    "/task/pull": ServiceType(),
    "/task/commit": ServiceType(),
    "/task/refresh": ServiceType(),
    "/task/heartbeat": ServiceType(),
}
const G = {
    tasksDir: config.rootPath + "/src/tasks",
    tasksLogPath: config.rootPath + "/src/logs/tasks.db",
    tasks: [],
    // 排除不更新的key
    excludeKey: [
        "id",
        "path",
        "createTime",
        "historyResponse"
    ],
}

const assign = {
    // 缓存请求 { req, res, name }
    requestMap: {
        // 'getUser': [{ req, res, name }, { req, res, name }]
    },
    // 缓存任务
    taskMap: {
        // "getUser": [task, task]
    },
    errTaskMap: [],
    // 过期队列
    runTaskQueue: [
        // {id: "task的id", time: "过期时间"}
    ],
    timeoutId: null,

    init(){
        Object.keys(this.requestMap).forEach(key => {
            delete this.requestMap[key];
        });
        Object.keys(this.taskMap).forEach(key => {
            delete this.taskMap[key];
        });
        this.runTaskQueue.splice(0, this.runTaskQueue.length);
        this.errTaskMap.splice(0, this.errTaskMap.length);

        G.tasks.forEach(task=>{
            // 将任务添加到 taskMap
            try{
                this.addTask(task);
            }catch(err){
                this.errTaskMap.push({
                    err, task
                });
                console.error(`任务添加到taskMap出错:`, err.message, "任务id:" , task.id, "任务路径path:", task.path);
            }
        });

        this.queueTimeout();
    },
    addRequest(req, res, progress){
        // 从taskMap中查询是否有这个任务
        // 有就发给他运行
        // 然后加入过期队列
        if(typeof progress !== "string" || progress.length < 1) throw new Error("步骤名称progress不存在!");
        let tasks = this.taskMap[progress];
        if(!(tasks instanceof Array) || tasks.length < 1) {
            // 加入等待队列
            if(!(this.requestMap[progress] instanceof Array)) {
                this.requestMap[progress] = [];
            }
            let reqObj = { req, res, progress };
            this.requestMap[progress].push(reqObj);

            req.socket.on('close', (hadError) => {
                try{
                    if(this.requestMap[progress] instanceof Array) {
                        console.log('客户端断开连接');
                        let index = this.requestMap[progress].indexOf(reqObj);
                        if(-1 < index) {
                            this.requestMap[progress].splice(index, 1);
                        }
                    } else {
                        // 数据已经被清空，不管
                    }
                }catch(e) {
                    console.log("清理连接时出现异常", e.message);
                }
            });

        } else {
            // 获取到任务
            let task = tasks.splice(0,1)[0];
            if(task) {
                this.addTaskQueue(task);
            }
            res.send(task);
            return true;
        }
        return false;
    },
    /**
     * 将任务添加到队列中，不会将任务添加到 G.tasks 中
     * @param {Task} task 
     * @returns 
     */
    addTask(task){
        if(!(task.progressList instanceof Array)) throw new Error("任务的progressList属性必须是一个数组");
        if(task.progressList.length < 1) throw new Error("任务的progressList必须存在需要执行的脚本");
        if(task.progressList.length < 1) throw new Error("任务的progressList必须存在需要执行的脚本");

        let name = task.progressList[task.progress]
        if(typeof name !== "string" || name.length < 1) throw new Error("步骤不存在!任务的progress属性是progressList步骤列表的下标!");
        
        if(config.openAmqp){
            // 发送到队列中
            amqp.getConnection().then(async (conn) => {
                try{
                    // 创建一个通道
                    const channel = await conn.createChannel();
                    // 检查队列是否存在，不存在则创建
                    await channel.assertQueue(name);
                    
                    // 发送消息
                    const message = JSON.stringify(task, null, 2);
                    channel.sendToQueue(name, Buffer.from(message), { persistent: true });
                    console.log(`已发送: ${message}`)
                }catch(e) {
                    console.error('创建通道错误:', err);
                }
                // 当不再需要连接时，将其返回给连接池
                amqp.releaseConnection(conn);
            }).catch(err => {
              console.error('获取链接错误:', err);
            })

            return true;
        } else {
            // 从requestMap中查询是否有谁能处理这个任务
            let reqs = this.requestMap[name];
            if(reqs instanceof Array && 0 < reqs.length) {
                for (let i = 0; i < reqs.length; i++) {
                    const req = reqs[i];
                    try{
                        // 有就发给他运行
                        req.res.send(task);
                        // 然后加入过期队列
                        this.addTaskQueue(task);
                        // 删除这些无效的请求列表
                        reqs.splice(0, i);
                        return true;
                    }catch(e){
                        console.log("发送时出现异常", e);
                    }
                }
            } else {
                // 加入队列等待
                if(!(this.taskMap[name] instanceof Array)) {
                    this.taskMap[name] = [];
                }
                this.taskMap[name].push(task);
            }
        }
        return false;
    },
    queueDetection(){
        // 检测是否过期
        let dn = Date.now();
        let i = 0;
        for (; i < this.runTaskQueue.length; i++) {
            const expired = this.runTaskQueue[i];
            if(dn < expired.time) {
                break;
            }
        }
        if(0 < i) {
            // 清空过期的所有数据，包括不匹配该条件的（dn < expired.time）
            this.runTaskQueue.splice(0, i);
        }
        this.queueTimeout();
    },
    queueTimeout(){
        if(this.timeoutId != null) {
            clearTimeout(this.timeoutId);
        }
        // 定时器检测是否过期
        if(0 < this.runTaskQueue.length) {
            // 每次定时都采用最近的一个作为定时
            setTimeout(()=>{
                this.queueDetection();
            }, Date.now() - this.runTaskQueue[0].time)
        }
    },
    async commitTask(task){
        // 从队列中删除task
        this.delTaskQueue(task.id);
        // 其他的调用 updateTask 处理
        return await updateTask(task);
    },
    heartbeat(taskId) {
        // 刷新队列中指定的任务
        if(this.delTaskQueue(taskId)) {
            let task = G.tasks.find(task=>task.id === taskId);
            if(task) {
                this.addTaskQueue(task);
                return true;
            } else throw new Error(taskId + "任务不存在!");
        } else throw new Error(taskId + "不在队列中!");
    },
    /**
     * 删除过期队列中指定的任务
     * @returns 
     */
    delTaskQueue(taskId){
        let re = false;
        for (let i = 0; i < this.runTaskQueue.length; i++) {
            const expired = this.runTaskQueue[i];
            if(expired.id === taskId) {
                this.runTaskQueue.splice(i, 1);
                re = true;
                break;
            }
        }
        return re;
    },
    /**
     * 添加任务到过期队列中
     * @param {Task} task 
     */
    addTaskQueue(task) {
        // 标记为运行中
        task.runStatus = true;

        this.runTaskQueue.push({
            id: task.id,
            time: Date.now() + config.expired * 1000
        });
        
        this.queueTimeout();
    }
}

init();
module.exports = routers;

function init(){
    refreshTasks();
    let services = {};
    createServices(services);

    appendServices(routers, services);
}

function createServices(services){
    services["/task/refresh"] = async function(req, res, body) {
        refreshTasks();
        return true;
    }
    services["/task/pull"] = async function(req, res, body) {
        // 根据脚本名字，下发一个脚本给请求者，否则挂起
        assign.addRequest(req, res, body.progress);
        return util.result.MANUAL_REPLY;
    }
    services["/task/commit"] = async function(req, res, body) {
        let task = await assign.commitTask(body);
        return task;
    }
    services["/task/heartbeat"] = async function(req, res, body) {
        if(body.ids instanceof Array){
            body.ids.forEach(id=>{
                assign.heartbeat(id);
            })
        }
        return true;
    }
    services["/task/add"] = async function(req, res, body) {
        let task = await addTask(body);
        // 触发响应到脚本
        assign.addTask(task);
        return task;
    }
    services["/task/del"] = function(req, res, body) {
        if(body.ids instanceof Array){
            body.ids.forEach(id=>{
                removeTask(id, true);
            })
        }
        return true;
    }
    services["/task/up"] = function(req, res, body) {
        updateTask(body);
        return true;
    }
    services["/task/all"] = async function(req, res, body) {
        // 分页 limit page 
        let bodyPage = util.anyPage(body);
        let start = bodyPage.page * bodyPage.limit;
        let end = start + bodyPage.limit;
        let list = G.tasks.slice(start, end);
        let result = {
            total: G.tasks.length,
            limit: bodyPage.limit,
            page: bodyPage.page,
            list: list,
        }

        if(body.detail && 0 < list.length) {
            // 读取全部数据并返回
            let taskDetails = [];
            for(var i=0; i< result.length; i++) {
                let detail = await readTask(result[i].path);
                if(!detail) {
                    if(result[i].historyResponse instanceof Array) {
                        result[i].historyResponse.push("读取数据为空,文件可能不存在!");
                    } else {
                        result[i].historyResponse = ["读取数据为空,文件可能不存在!"];
                    }
                    detail = result[i];
                }
                taskDetails.push(detail);
            }
            result.list = taskDetails;
        }
        let info = readInfo();
        result.info = info;
        return result;
    }
    services["/task/get"] = async function(req, res, body) {
        let task = G.tasks.get(body.id);
        if(body.detail) {
            return await readTask(task.path);
        }
        return task;
    }
}

/**
 * 读取总数
 * @returns 
 */
function readInfo(){
    let data = {
        dn: Date.now(),
        requestMap: {},
        requestTotal: 0,
        taskMap: {},
        taskMapTotal: 0,
        errTaskMap: assign.errTaskMap,
        errTaskMapTotal: assign.errTaskMap.length,
        runTaskQueue: assign.runTaskQueue,
        runTaskQueueTotal: assign.runTaskQueue.length,
        timeoutId: assign.timeoutId,
        tasksTotal: G.tasks.length,
        expiredTime: config.expired
    }
        
    // 计算总数
    for (const key in assign.requestMap) {
        let len = assign.requestMap[key].length;
        if(0 < len) {
            data.requestMap[key] = len;
            data.requestTotal += len;
        }
    }
    for (const key in assign.taskMap) {
        let len = assign.taskMap[key].length;
        if(0 < len) {
            data.taskMap[key] = len;
            data.taskMapTotal += len;
        }
    }

    return data;
}

/**
 * 刷新
 */
async function refreshTasks(){
    let start = Date.now();
    console.log("刷新任务列表中...");
    G.tasks = [];
    let names = await fs.promises.readdir(G.tasksDir);
    // 读取文件并添加到tasks
    for (let i = 0; i < names.length; i++) {
        const name = names[i];
        const filePath = path.join(G.tasksDir, name);
        let task = await readTask(filePath);
        if(task.path != filePath) {
            task.path = filePath;
        }

        if(task != null) G.tasks.push(task);
    }
    console.log(`刷新任务列表已完成(${G.tasks.length}/${names.length})，耗时${Date.now() - start}ms`);
    assign.init();
}



/**
 * 更新任务
 * @param {Task} body 
 */
async function updateTask(body){
    // 更新有的字段
    let task = G.tasks.find(task=>task.id == body.id);
    if(!task) return;
    
    for (const key in task) {
        if(G.excludeKey.find(key) == undefined && body[key] !== undefined){
            task[key] = body[key];
        }
    }
    await writeTask(task);
    try{
        // 检测任务是否已经完成
        if(task.progressList.length <= task.progress) {
            console.log(task.id + "任务已完成");
            removeTask(task.id);
            return task;
        }
    } catch(err){
        console.error("检测任务是否已完成出错!", err);
    }

    // 检测是否存在队列中，不存在就加入队列
    try{
        assign.addTask(task);
    }catch(e){
        console.error("加入队列失败!", e);
    }
    return task;
}

/**
 * 创建task
 * @param {Task} body 
 * @returns 
 */
async function addTask(body){
    // 1. 创建任务信息
    let task = new Task(body);
    // 2. 检测并注册id
    generateId(task);
    // 锁定路径
    task.path = G.tasksDir + "/" + task.id;
    // 3. 写到文件中
    await writeTask(task);
    // 4. 返回
    return task;
}

/**
 * 读取task文件数据
 * @param {String} path 
 * @param {Boolean} create 是否是创建模式，创建模式时不存在task文件就会创建一个空的task对象
 * @returns {Task}
 */
async function readTask(path, create) {
    let task;
    try {
        task = await fs.promises.readFile(path, 'utf8');
        task = JSON.parse(task);
    } catch (error) {
        console.error(`读取文件错误: ${error}`);
        if(create) {
            // 创建模式下生成一个空的 task 对象
            task = new Task();
        }
    }
    return task;
}
/**
 * 将任务写到文件中进行持久化
 * 不会使用当前任务的 historyResponse 属性进行持久化
 * @param {Task} task 
 */
async function writeTask(task) {
    // 1. 通过task.path读取出之前的task对象
    let data = readTask(task.path, true);

    // 2. 读取历史响应数据historyResponse，合并当前响应数据response
    const historyResponse = data ? data.historyResponse : [];
    if(task.response) historyResponse.push(task.response);

    // 3. 将数据写回到task.path文件中
    const newData = { ...task, historyResponse };
    await fs.promises.writeFile(task.path, JSON.stringify(newData, null, 2));
}
/**
 * 创建id，任务名尽量不要使用特殊符号，最后会拿去创建文件
 * id规则: 任务名+时间+序号
 * @param {Task} task 
 */
function generateId(task){
    let progressList = task.progressList.join('-')
    let dn = Date.now();
    let num = 0;
    if(!(task.progressList instanceof Array)) task.progressList = [];
    let perfix = task.progressList.length < 1 ? "none" : task.progressList.join("+");
    do{
        num++;
        id = `${perfix}+${dn}+${num}`;
    }while (G.tasks.find(task => task.id == id));
    task.id = id;
    G.tasks.push(task);
    task.path = path.join(G.tasksDir, task.id);
    return task;
}

/**
 * 通过id删除文件记录
 * @param {String} id 
 * @param {Boolean} saveToLog 是否保存到日志中
 */
async function removeTask(id, saveToLog) {
    let task = G.tasks.find(task=>task.id === id);
    let index = G.tasks.indexOf(task);
    if(-1 < index) {
        G.tasks.splice(index, 1);
    }
    
    let filePath = task.path;
    try {
        // 检查文件是否存在
        await fs.promises.access(filePath, fs.constants.F_OK);

        if (saveToLog) {
            // 文件存在，读取文件内容
            const data = await fs.promises.readFile(filePath, 'utf8');

            // 将文件内容追加到日志文件中
            await fs.promises.appendFile(G.tasksLogPath, data + '\n');

            // 删除文件
            await fs.promises.unlink(filePath);
        } else {
            // 直接删除文件
            await fs.promises.unlink(filePath);
        }
    } catch (error) {
        if (error.code === 'ENOENT') {
            console.log('文件不存在');
        } else {
            console.error('处理文件时发生错误:', error);
            throw error;
        }
    }
}